Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #595 +/- ##
==========================================
- Coverage 90.48% 90.23% -0.25%
==========================================
Files 180 189 +9
Lines 9109 9608 +499
Branches 814 877 +63
==========================================
+ Hits 8242 8670 +428
- Misses 867 938 +71
🚀 New features to boost your workflow:
|
|
/cc @draffensperger |
| protected patch() { | ||
| this._logger.debug('applying patch to', this.moduleName, this.version); | ||
|
|
||
| if (isWrapped(XMLHttpRequest.prototype.open)) { |
There was a problem hiding this comment.
Why would this already be patched? Shouldn't we just no-op if it is already wrapped?
There was a problem hiding this comment.
with web there is always some probability that something can be destroyed and recreated again. This way it will always work correctly
There was a problem hiding this comment.
Since this is patching a global, would it not patch if multiple components on the same page are instrumented with separate tracers/configs?
There was a problem hiding this comment.
copy paste here my reply from below
We are using shimmer which doesn't support multiple wrap of the same function so if you even register multiple tracers with the same plugins, and try to unpatch just one, it will unpatch the global successfully anyway. So if the decision to use shimmer was done my assumption is that we are able to instrument using the plugins once only. Otherwise the code will be broken anyway.
packages/opentelemetry-core/src/trace/instrumentation/basePluginUtils.ts
Show resolved
Hide resolved
| ): OpenFunction | any { | ||
| plugin._createSpan(this as XMLHttpRequestWrapped, url, method); | ||
|
|
||
| if (async) { |
There was a problem hiding this comment.
it is always safer to use original.apply(this, arguments) because we can't account for users passing incorrect values or the incorrect number of values. It also removes the need for this check.
There was a problem hiding this comment.
It is the same here.
You don't have arguments word in typescript , so you would still declare array of arguments and define type for it, which would be quite weird anyway.
Besides that check also the whole code to see more context. The async will always be set to true and there are reasons for that check here. It is also worth to mention that it was ported from open census.
There was a problem hiding this comment.
You do have the arguments keyword in typescript.
It is possible to call open with async: false and user not-null. In this case you are changing the arguments the user has called the function with.
We cannot assume that users will use the API properly as documented and it is much easier and safer to directly pass their arguments. If the user incorrectly calls open with async: true I am suggesting something like this (tests pass with this version):
protected _patchOpen() {
return (original: Function): OpenFunction => {
const plugin = this;
return function patchOpen(
this: XMLHttpRequest,
method: string,
url: string,
async?: boolean,
user?: string | null,
pass?: string | null
): ReturnType<OpenFunction> {
plugin._createSpan(this as XMLHttpRequestWrapped, url, method);
return original.apply(this, arguments);
/*
If you would rather use `call` this can be done easily
return original.call(this, ...arguments);
*/
};
};
}Regarding the performance difference between apply and call, it may be the case that call is faster than apply, but in this case the if statement would provide more overhead than the difference.
There was a problem hiding this comment.
still cannot use apply anyway, as I mentioned previously please read the spec, and why it needs to force the async
There was a problem hiding this comment.
If true, notification of a completed transaction is provided using event listeners.
There was a problem hiding this comment.
What about that means it must be true? If it is false (synchronous), then you just finish the span after calling the original function. If user sets async false in a context that it is not allowed (some browsers only allow sync requests in workers), then
The way your code is currently, it changes the behavior of user code behind the scenes. If I am a user and I make a synchronous xhr request, I expect it to be made synchronously not transparently rewritten to be async behind the scenes by my telemetry provider.
There was a problem hiding this comment.
https://github.com/census-instrumentation/opencensus-web/blob/68aa988732e03f2de49ebc0213c3b2525aa5b68b/packages/opencensus-web-instrumentation-zone-peer-dep/src/monkey-patching.ts#L31
and
https://github.com/census-instrumentation/opencensus-web/blob/68aa988732e03f2de49ebc0213c3b2525aa5b68b/packages/opencensus-web-instrumentation-zone-peer-dep/src/monkey-patching.ts#L54
and
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests
Can you guys @draffensperger @mayurkale22 have your input here too, thx :).
There was a problem hiding this comment.
and one more Synchronous XHR is now in deprecation state.
There was a problem hiding this comment.
I understand that it's deprecated, but that doesn't mean users can't or won't use it and the telemetry library shouldn't silently change the behavior of the program.
should we tallk about this in the SIG tomorrow?
| }; | ||
| plugin._addHeaders(this, currentSpan); | ||
| } | ||
| return original.call(this, body); |
There was a problem hiding this comment.
| return original.call(this, body); | |
| return original.apply(this, arguments); |
There was a problem hiding this comment.
- call is faster
- we already know there is just one para, we know it's type so we can use it and typescript will also validate the type of this is correcty
- You don't have
argumentsword in typescript , so you would still declare array of aguments and define type for it, which would be quite weird anyway
There was a problem hiding this comment.
and the same here, thx
@draffensperger @mayurkale22 ^^
packages/opentelemetry-core/src/trace/instrumentation/basePluginUtils.ts
Show resolved
Hide resolved
packages/opentelemetry-core/src/trace/instrumentation/basePluginUtils.ts
Show resolved
Hide resolved
packages/opentelemetry-plugin-xml-http-request/src/enums/AttributeNames.ts
Show resolved
Hide resolved
packages/opentelemetry-plugin-xml-http-request/src/enums/AttributeNames.ts
Show resolved
Hide resolved
|
marking temporary as WIP - working on better detection for cors |
| */ | ||
| export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> { | ||
| readonly component: string = 'xml-http-request'; | ||
| readonly version: string = '0.3.0'; |
There was a problem hiding this comment.
That would assume that the user has the same version of the plugin and of core installed which may not always be the case. @obecny and I had talked about adding the autogenerated VERSION file to all lerna packages, but nothing has been done yet.
| ): OpenFunction | any { | ||
| plugin._createSpan(this as XMLHttpRequestWrapped, url, method); | ||
|
|
||
| if (async) { |
There was a problem hiding this comment.
You do have the arguments keyword in typescript.
It is possible to call open with async: false and user not-null. In this case you are changing the arguments the user has called the function with.
We cannot assume that users will use the API properly as documented and it is much easier and safer to directly pass their arguments. If the user incorrectly calls open with async: true I am suggesting something like this (tests pass with this version):
protected _patchOpen() {
return (original: Function): OpenFunction => {
const plugin = this;
return function patchOpen(
this: XMLHttpRequest,
method: string,
url: string,
async?: boolean,
user?: string | null,
pass?: string | null
): ReturnType<OpenFunction> {
plugin._createSpan(this as XMLHttpRequestWrapped, url, method);
return original.apply(this, arguments);
/*
If you would rather use `call` this can be done easily
return original.call(this, ...arguments);
*/
};
};
}Regarding the performance difference between apply and call, it may be the case that call is faster than apply, but in this case the if statement would provide more overhead than the difference.
|
You may also want to do the same with the other maps that have possibility of leaks: which then removes the need for |
|
@dyladan not all can be done and taskCount is still needed, but all things that depends on xhr has been moved. |
draffensperger
left a comment
There was a problem hiding this comment.
💯 👍 Great job on this.
I'm giving approval since to me this semantically captures what we should be doing in this plugin. I left a couple more minor comments/suggestions and in particular I think the issue around the cast vs. use of the span API is relevant to fix if we can. But this is great!!
As an aside, I saw the comment thread about this assuming there is a trace context set up, which to me is a relevant comment, and highlights the need for this plugin to work in conjunction with other plugins that set up a "root" span of sorts, e.g. a user interaction tracing plugin, a root span from document load, etc.
| } | ||
| } | ||
| if (!addHeaderOnDifferentOrigin) { | ||
| this._logger.warn('Cannot set headers on different origin'); |
There was a problem hiding this comment.
I think this will be a pretty routine case that the user sends an XHR to a different origin that they don't want to pass along trace headers for (that is, at least until the browser spec can give an exception for CORS for the W3C trace headers)
Given that, would it make sense to remove this warning log and corresponding if block?
There was a problem hiding this comment.
I might be wrong but my understanding is that we might break something with the request if we force to add headers. The headers are only added when origin is the same. If not then I check if user allows to add the headers based on config. If not then the headers are dropped and warning is being show. I cannot remove the if block (to prevent adding headers) but only warning if that is something you want me to do ?
dyladan
left a comment
There was a problem hiding this comment.
This looks real good!
From my perspective I really only have one outstanding question about forcing all calls to open to be async even when the user passes false. I'd like to get @draffensperger and @mayurkale22 opinion on this.
It looks like the dependency on web in this version is limited to only functions that don't require casts to our sdk types so they should be compatible even with an sdk other than ours being used. 👍
| ): void { | ||
| plugin._createSpan(this, url, method); | ||
|
|
||
| return original.call(this, method, url, true, user, pass); |
There was a problem hiding this comment.
Creating a new thread on this line as the old one is deep in the history and on a line that has been outdated for a long time.
There was a problem hiding this comment.
Yeah, I think I missed this in my review, but I definitely agree that we should preserve the original behavior of the XHR function including the async parameter.
| const onClick = () => { | ||
| for (let i = 0, j = 5; i < j; i++) { | ||
| const span1 = webTracerWithZone.startSpan(`files-series-info-${i}`, { | ||
| parent: webTracerWithZone.getCurrentSpan() |
There was a problem hiding this comment.
I believe if there is no current span, this span will just be a root span, which is the correct behavior. I think my comment about having a nice root span for XHRs is more like a future feature and not so much an issue with this code.
|
@draffensperger @mayurkale22 @dyladan changed the code with regards to our meeting |
|
@obecny just wanted to confirm, do you have any outstanding work here or PR is ready to merge? |
Nothing left from my side |
Which problem is this PR solving?
Short description of the changes
It adds auto instrumentation for XMLHttpRequest including trace headers